package br.unisul.redeNeural;

import java.util.ArrayList;
import java.util.List;

/**
 * Classe que Representa a Rede Neural Artificial(RNA) MultiLayer Perceptron.<br>
 * Esta RNA possui suporte a multicamadas, e vies.<br>
 * Deve se diferenciar o tipo de dados que se irá trabalhar, binarios ou bipolares.
 * @author Joelson Fernandes
 *
 */
public class MLP extends RedeNeural implements java.io.Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 8914767637230486068L;

	/**
	 * Rede tipo Binaria
	 */
	public static final int BINARIA = 0;

	/**
	 * Rede tipo Bipolar
	 */
	public static final int BIPOLAR = 1;

	/**
	 * Tipo da Rede
	 */
	private int tipo;

	/**
	 * Cria uma MLP conforme a {@link Configuracao}
	 * @param configuracao - configuracão da {@link RedeNeural}
	 * @param tipoRede - tipo que ela representa (binaria, bipolar)
	 */
	public MLP(Configuracao configuracao, int tipoRede) {
		super(configuracao);
		this.tipo = tipoRede;
		ConfiguracaoMLP c = new ConfiguracaoMLP(this);
		setFuncaoAtivacao(c);
		setAtualizarVies(c);
		setCalculoSaida(c);
		setVerificarSaida(c);


	}

	public void treinar(ParTreino parTreino) throws RedeNeuralException {
		inicializarValores();
		while(!verificarParada()){
			for (int i = 0; i < parTreino.getQuantidade(); i++) {

				atualizarEntradas(parTreino.getEntrada(i));


				for (int j = 1; j < quantidadeCamadas(); j++) {
					Neuronio neuroniosTemp[] = getNeuronios(j);
					for (int k = 0; k < neuroniosTemp.length ; k++) {
						Neuronio[] neuronios = getNeuroniosEntrada(j, k);
						Peso[] pesos = getPesosEntrada(j, k);
						calcularSaidaNeuronio(neuronios, pesos, neuroniosTemp[k]);
					}
				}
				/**
				 * Atualizar os pesos
				 */
				Neuronio saidas[] = getSaidas();//pega os neuronios da camda de saida
				double saidasDesejadas[] = parTreino.getSaida(i);//pega as saidas desejadas

				/**calculo do erro das saidas - deltaK*/
				double errosCamadaSaida[] = new double[saidas.length];

				/**calcular o erro da camada de saida*/
				for (int j = 0; j < saidas.length; j++) {
					double saidaObtida = saidas[j].getValorNeuronio();
					double erro = (saidasDesejadas[j] - saidaObtida);
					erro *= funcaoAtivacaoLinha(saidas[j].getValorNeuronio());
					errosCamadaSaida[j] = erro;
				}

				//pega os neuronios que estao na camada anterior a da camada de saida
				Neuronio[] entrada = getNeuronios(quantidadeCamadas()-2);
				//cria uma matriz para atualizar os pesos entre a camada de saida e a cama anterior
				double valorParaCamadaSaida[][] = new double[entrada.length][saidas.length];

				//Atualizar o vies da camada de saida
				for (int j = 0; j < saidas.length; j++) {
					atualizarVies(saidas[j].getVies(), errosCamadaSaida[j] * getConfiguracao().getTaxaAprendizado());
				}
				//
				for (int k = 0; k < valorParaCamadaSaida.length; k++) {
					for (int l = 0; l < valorParaCamadaSaida[k].length; l++) {
						valorParaCamadaSaida[k][l] += errosCamadaSaida[l] * getConfiguracao().getTaxaAprendizado() 
						* entrada[k].getValorNeuronio(); 
					}
				}
				//						backpropagation(erroSaida, quantidadeCamadas()-1 , j);
				List<double[][]> lista = new ArrayList<double[][]>();

				backprogation(quantidadeCamadas()-2,errosCamadaSaida, lista);

				lista.add(valorParaCamadaSaida);

				for (int j = 0; j < lista.size(); j++) {
					double [][]valores = lista.get(j);
					Peso [][] atualiza = getPesos(j);
					for (int k = 0; k < atualiza.length; k++) {
						for (int l = 0; l < atualiza[k].length; l++) {
							atualizarPeso(atualiza[k][l], valores[k][l]);
						}
					}
				}



			}
		}
	}


	/**
	 * Backpropation do erro
	 * @param camada - camada a ser atualizada
	 * @param erroCamadaAnterior - valores do erro da camada anterior
	 * @param lista - lista com os valores para atualização
	 */
	private void backprogation(int camada, double erroCamadaAnterior[], List<double[][]> lista){
		if(camada>0){
			Neuronio neuroniosCamada[] = getNeuronios(camada);//neuronios dessa cama
			Peso pesosCamada[][] = getPesos(camada);//pegar os pesos que liga esta camada com a proxima
			double erroNeuronios[] = new double[neuroniosCamada.length];//valor para calcular os erros

			for (int i = 0; i < pesosCamada.length; i++) {
				double temp = 0;
				for (int j = 0; j < pesosCamada[i].length; j++) {
					temp+= erroCamadaAnterior[j] * pesosCamada[i][j].getValor();
				}
				erroNeuronios[i] = temp * funcaoAtivacaoLinha(neuroniosCamada[i].getValorNeuronio());
			}

			for (int i = 0; i < neuroniosCamada.length; i++) {
				atualizarVies(neuroniosCamada[i].getVies(), erroNeuronios[i] * getConfiguracao().getTaxaAprendizado());
			}

			//Neuronios da camada anterior a esta
			Neuronio neuroniosCamadaEntrada[] = getNeuronios(camada - 1);
			//Valores para atualizar os pesos que est�o entre a camada anterior e esta
			double valoresAtualizarCamada[][] = new double[neuroniosCamadaEntrada.length][erroNeuronios.length];
			for (int i = 0; i < valoresAtualizarCamada.length; i++) {
				for (int j = 0; j < valoresAtualizarCamada[i].length; j++) {
					valoresAtualizarCamada[i][j] = erroNeuronios[j]*neuroniosCamadaEntrada[i].getValorNeuronio()*getConfiguracao().getTaxaAprendizado();
				}
			}
			backprogation(camada -1, erroNeuronios, lista);
			lista.add(valoresAtualizarCamada);
		}
	}


	/***
	 * Aplicar função linha 
	 * @param fx - valor a ser aplicada a função
	 * @return resultado da função
	 */
	private double funcaoAtivacaoLinha(double fx) {
		if (BINARIA == tipo) {
			return fx * (1 - fx);
		} else {
			return ((1 + fx) * (1 - fx)) / 2;
		}
	}

	/**
	 * @return the tipo
	 */
	public int getTipo() {
		return tipo;
	}

	
	


}
